﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DDD.Core {

    /// <summary>
    /// トランスフォームメッシュ･コンポーネント
    /// </summary>
    /// <remarks>
    /// <see cref="Transformer"/>は静的メッシュに加えてGPUによる頂点の変形を伴うメッシュです。
    /// 変形とは
    /// (1)変形前の頂点データ(<see cref="VertexBuffer"/>を、
    /// (2)変形用のアピアランス<see cref="Appearance"/>を使って
    /// (3)変形後のデータを<see cref="FeedbackBuffer"/>に書き込む事です。
    /// 変形によってオリジナルの頂点データが書き換えられる事はありません。
    /// 基底クラスの<see cref="Mesh.VertexBuffer"/>と<see cref="Transformer.VertexBuffer"/>に
    /// 同一の<see cref="VertexArray"/>をセットする事でコピーなしに変形後の頂点データを描画可能です。
    /// 変形後の頂点データを次の入力としたい場合は<see cref="FeedbackBuffer"/>を<see cref="VertexBuffer"/>にコピーします。
    /// 具体的な方法は忘れました(ひどい・・・)。コピーではなくダブルバッファリングする方法は考えていません。
    /// たぶん問題にはならないはず。
    /// </remarks>
    public class Transformer : Component {
        #region Field
        VertexBuffer[] vbufs;
        FeedbackBuffer[] fbufs;
        Appearance[] apps;
        #endregion


        #region Constructor
        /// <summary>
        /// サブメッシュ1つのコンストラクター
        /// </summary>
        public Transformer ()
            : this (1) {
        }

        /// <summary>
        /// 複数のサブメッシュを使うコンストラクター
        /// </summary>
        /// <param name="numSubmesh"></param>
        public Transformer (int numSubmesh){
            if (numSubmesh <= 0) {
                throw new ArgumentException ("NumSubmesh is invalid");
            }
            this.fbufs = new FeedbackBuffer[numSubmesh];
            this.apps = new Appearance[numSubmesh];
            this.vbufs = new VertexBuffer[numSubmesh];
        }
        #endregion

        #region Property

        /// <summary>
        /// 変形に使用されるトランスフォーム情報を列挙します
        /// </summary>
        /// <remarks>
        /// 全てのサブメッシュについて設定済みの値を返します｡
        /// </remarks>
        public IEnumerable<Transform> Transforms {
            get {
                var list = new List<Transform> ();
                for (var i = 0; i < SubmeshCount; i++) {
                        list.Add (new Transform ((vbufs[i] == null) ? vbufs[0] : vbufs[i],
                                                            (fbufs[i] == null) ? fbufs[0] : fbufs[i],
                                                            (apps[i] == null) ? apps[0] : apps[i]));
                    
                }
                return list;
            }
        }

        /// <summary>
        /// サブメッシュの総数
        /// </summary>
        public int SubmeshCount {
            get { return vbufs.Length; }
        }

        /// <summary>
        /// 変形前の頂点データを保持する頂点バッファー
        /// </summary>
        /// <remarks>
        /// 変形によってこのバッファーが書き換えられる事はありません
        /// </remarks>
        public VertexBuffer VertexBuffer {
            get { return vbufs[0]; }
        }

        /// <summary>
        /// フィードバック・バッファー
        /// </summary>
        /// <remarks>
        /// トランスフォームフィードバックの出力を書き込むバッファーです。
        /// </remarks>
        public FeedbackBuffer FeedbackBuffer {
            get { return fbufs[0]; }
        }

        /// <summary>
        /// 頂点変形で使用されるアピアランス(シェーダー)
        /// </summary>
        /// <remarks>
        /// フラグメントシェーダーなど一部の機能は使用しません。
        /// </remarks>
        public Appearance Appearance {
            get { return apps[0]; }
        }

        /// <summary>
        /// 頂点変形で使用される(変形前の)頂点バッファー
        /// </summary>
        public IEnumerable<VertexBuffer> VertexBuffers {
            get { return vbufs.Where (x => x != null); }
        }

        /// <summary>
        /// トランスフォーム・フィードバックバッファーを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// 列挙子の中にnullは含まれません。
        /// </remarks>
        public IEnumerable<FeedbackBuffer> FeedbackBuffers {
            get { return fbufs.Where (x => x != null); }
        }

        /// <summary>
        /// 頂点変形で使用されるnullでないアピアランス(シェーダー)を列挙する列挙子
        /// </summary>
        /// <remarks>
        /// トランスフォームで使用されるアピアランス(つまり<see cref="ShaderProgram"/>)を列挙します。
        /// 通常トランスフォームはこのシェーダーを使って実行されます。
        /// </remarks>
        public IEnumerable<Appearance> Appearances {
            get { return apps.Where (x => x != null); }
        }


        /// <summary>
        /// このオブジェクトが参照する全DDDオブジェクトを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// 参照にnullは含まれません。同一のオブジェクトが複数出現する可能性があります。
        /// </remarks>
        public override IEnumerable<Object> References {
            get {
                var objs = base.References.ToList ();
                objs.AddRange (fbufs);
                objs.AddRange (vbufs);
                objs.AddRange (apps);
                objs.RemoveAll (x => x == null);
                return objs;
            }
        }
        #endregion

        #region Method


        /// <summary>
        /// 0番目のフィードバックバッファーを取得します
        /// </summary>
        /// <returns></returns>
        public FeedbackBuffer GetFeebackBuffer () {
            return fbufs[0];
        }

        /// <summary>
        /// i番目のフィードバックバッファーを取得します
        /// </summary>
        /// <param name="index">サブメッシュ番号</param>
        /// <returns>フィードバックバッファー</returns>
        public FeedbackBuffer GetFeedbackBuffer (int index) {
            if (index < 0 || index >= SubmeshCount) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            return fbufs[index];
        }



        /// <summary>
        /// 0番目のサブメッシュに頂点変形後の出力を書き込むフィードバックバッファーをセットします
        /// </summary>
        public void SetFeedbackBuffer (FeedbackBuffer vbuf) {
            this.fbufs[0] = vbuf;
        }


        /// <summary>
        /// サブメッシュに頂点変形後の出力を書き込む頂点バッファーをセットします
        /// </summary>
        /// <remarks>
        /// トランスフォームフィードバックの出力はこのバッファーに書き込まれます。
        /// </remarks>
        /// <param name="index">サブメッシュ番号</param>
        /// <param name="fbuf">変形後の出力を書き込むフィードバックバッファー</param>
        public void SetFeedbackBuffer (int index, FeedbackBuffer fbuf) {
            if (index < 0 || index >= SubmeshCount) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            this.fbufs[index] = fbuf;
        }

        /// <summary>
        /// 0番目のサブメッシュのトランスフォーム･アピアランスを取得します
        /// </summary>
        /// <returns>変形に使用されるアピアランス</returns>
        public Appearance GetAppearance () {
            return apps[0];
        }


        /// <summary>
        /// トランスフォーム･アピアランスを取得します
        /// </summary>
        /// <remarks>
        /// トランスフォームで使用されるアピアランス(つまり<see cref="ShaderProgram"/>)を取得します。
        /// </remarks>
        /// <param name="index">サブメッシュインデックス</param>
        /// <returns>アピアランス</returns>
        public Appearance GetAppearance (int index) {
            if (index < 0 || index >= SubmeshCount) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            return apps[index];
        }


        /// <summary>
        /// 0番目のサブメッシュのトランスフォーム・アピアランスをセットします
        /// </summary>
        /// <remarks>
        /// </remarks>
        /// <param name="app">アピアランス</param>
        public void SetAppearance (Appearance app) {
            this.apps[0] = app;
        }

        /// <summary>
        /// i番目のトランスフォームアピアランスをセットします
        /// </summary>
        /// <param name="index">サブメッシュ番号</param>
        /// <param name="app">アピアランス</param>
        public void SetAppearance (int index, Appearance app) {
            if (index < 0 || index >= SubmeshCount) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            this.apps[index] = app;

        }

        /// <summary>
        /// 0番目のサブメッシュのトランスフォーム･頂点バッファーを取得します
        /// </summary>
        /// <returns></returns>
        public VertexBuffer GetVertexBuffer () {
            return vbufs[0];
        }


        /// <summary>
        /// トランスフォーム･頂点バッファーを取得します
        /// </summary>
        /// <remarks>
        /// トランスフォームで使用されるアピアランス(つまり<see cref="ShaderProgram"/>)を取得します。
        /// </remarks>
        /// <param name="index">サブメッシュ番号</param>
        /// <returns>アピアランス</returns>
        public VertexBuffer GetVertexBuffer (int index) {
            if (index < 0 || index >= SubmeshCount) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            return vbufs[index];
        }


        /// <summary>
        /// 0番目のサブメッシュの変形前の頂点バッファーをセットします
        /// </summary>
        /// <remarks>
        /// トランスフォームアピアランスによって変形される頂点バッファーです。
        /// </remarks>
        /// <param name="vbuf">頂点バッファー</param>
        public void SetVertexBuffer (VertexBuffer vbuf) {
            this.vbufs[0] = vbuf;
        }

        /// <summary>
        /// i番目のトランスフォーム・頂点バッファーをセットします
        /// </summary>
        /// <param name="index">サブメッシュ番号</param>
        /// <param name="vbuf">頂点バッファー</param>
        public void SetVertexBuffer (int index, VertexBuffer vbuf) {
            if (index < 0 || index >= SubmeshCount) {
                throw new IndexOutOfRangeException ("Index is out of range");
            }
            this.vbufs[index] = vbuf;

        }

        /// <summary>
        /// 現在のオブジェクトの状態を表す文字列を返します。
        /// </summary>
        /// <returns>現在のオブジェクトを説明する文字列</returns>
        public override string ToString () {
            return string.Format ("TransformMesh: ");
        }
        #endregion

    }
}
